package com.nami.conver.domain.handler.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.TimeInterval;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.Scheduler;


import com.nami.conver.domain.handler.ConverSyncHandler;
import com.nami.conver.domain.handler.lock.StoreKeyLock;
import com.nami.conver.domain.pchat.model.ConverModel;
import com.nami.conver.domain.pchat.service.ConverTaskService;
import com.nami.conver.domain.repository.CacheConverRepository;
import jakarta.annotation.PostConstruct;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.core.utils.StringUtils;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static com.nami.gate.external.basic.constants.CommonConst.TASK_SEPARATOR;


@Slf4j
public abstract class AbstractConverSyncHandler implements ConverSyncHandler {

    /**
     * 处理刷盘时机，重要！重要！重要！
     * 刷到redis .晚上跑任务落库 能够使mysql io稳定
     */
    private static final int REFRESH_DURATION = 5;

    /**
     * caffeine 过期检查，缩小检查间隔。防止惰性懒加载。
     * 单位s
     */
    private static final int INTERVAL_FOR_CLEAN_CHECK = 5;

    protected ConverTaskService converTaskService;
    protected CacheConverRepository cacheConverRepository;

    private static final ScheduledExecutorService schedulerService = Executors.newSingleThreadScheduledExecutor();

    private static final ScheduledExecutorService SINGLE_CHECK = Executors.newScheduledThreadPool(1, r -> {
        Thread t = new Thread(r);
        t.setName("Caffeine-Scheduler");
        t.setDaemon(true);
        return t;
    });
    private static final Scheduler CHECK_EXPIRED_SCHEDULER = Scheduler.forScheduledExecutorService(SINGLE_CHECK);

    @PostConstruct
    public void init() {
        // 打印caffeine 运行状态
        schedulerService.scheduleAtFixedRate(() -> {
            this.stats();
        }, 10, 120, TimeUnit.SECONDS);

        // 检查caffeine 过期问题，
        SINGLE_CHECK.scheduleAtFixedRate(TASK_CACHE::cleanUp, 0, INTERVAL_FOR_CLEAN_CHECK, TimeUnit.SECONDS);
    }

    /**
     * 缓存过期时间拉大，可调节io
     */
    protected final Cache<String, Boolean> TASK_CACHE = Caffeine.newBuilder()
        .maximumSize(100000)
        .expireAfterWrite(REFRESH_DURATION, TimeUnit.SECONDS)
        .removalListener((key, value, cause) -> {
            try {
                TimeInterval timer = DateUtil.timer();
                ConverModel item = cacheConverRepository.get((String) key);
                if (item == null) {
                    log.warn("[conver-sync] 同步出现npe，key={}", key);
                    return;
                }
//                remoteConverService.insertOrUpd(item);
                // 改为写入redis cache
                // 此处会导致mysql 飙升，拆分到redis, 现在群聊3000人群消息处理稳定在20-30ms  性能拉到极致
                // 需要处理缓存关系
                // 是否需要再次拆分，更新为小批量存入缓存，比如300条。
                cacheConverRepository.writeConver(item);
                converTaskService.addAsyncWriteConverTask(buildTaskKey(item));
//                Thread.sleep(10);
                if (timer.interval() > 200) {
                    log.warn("[conver-sync] 同步出现耗时高, userId={}, targetId={} use={}ms", item.getUserId(), item.getTargetId(), timer.interval());
                }
            } catch (Exception var3) {
                log.error("[conver-sync] 会话落库 出现问题==>", var3);
            }

        })
        .scheduler(CHECK_EXPIRED_SCHEDULER)
        .build();

    protected static String buildTaskKey(ConverModel item) {
        return item.getAppKey() + TASK_SEPARATOR + item.getUserId() + TASK_SEPARATOR + item.getTargetId();
    }

    public AbstractConverSyncHandler(CacheConverRepository cacheConverRepository, ConverTaskService converTaskService) {
        this.cacheConverRepository = cacheConverRepository;
        this.converTaskService = converTaskService;
    }

    public void stats() {
        log.info("[conver-sync] caffeine health stats: {}", TASK_CACHE.stats());
    }

    @Override
    public void handler(String key) {
        try {
            if (StringUtils.isBlank(key)) {
                log.warn("[caffeine] key is null or empty");
                return;
            }
            StoreKeyLock.lock(key);
            doHandler(key);
        } finally {
            StoreKeyLock.unlock(key);
        }
    }

    abstract void doHandler(String key);

}
